package day30.zqStream;

import java.util.*;
import java.util.stream.*;
import java.util.function.*;

/**
 * 测试Java Stream API增强功能的类
 * 包含Stream API的各种操作示例，如过滤、映射、归约、收集等
 * <p>
 * Stream API是Java 8引入的一个重要特性，它提供了一种高效且易于使用的处理数据的方式。
 * Stream不是数据结构，而是对集合对象功能的增强，专注于对集合对象进行各种非常便利、
 * 高效的聚合操作（aggregate operation）或者大批量数据操作 (bulk data operation)。
 * </p>
 * <p>
 * Stream API的优势:
 * 1. 声明式编程风格：代码更简洁、更易读
 * 2. 支持函数式编程：可以传递行为而不是数据
 * 3. 支持并行处理：可以利用多核处理器提高性能
 * 4. 延迟计算：中间操作只在终端操作执行时才真正执行
 * </p>
 */
public class testZQStream {
    /**
     * 程序入口方法，演示Stream API的各种操作
     * <p>
     * 该方法创建了一个Person对象列表，然后通过一系列示例展示了Stream API的各种功能，
     * 包括过滤、映射、排序、归约、分组、分区、并行处理等操作。每个示例都详细说明了
     * 相应的API用法和实际应用场景，帮助理解Stream API的强大功能和使用方法。
     * </p>
     * @param args 命令行参数数组，本程序中未使用
     */
    public static void main(String[] args) {
        // 测试数据 - 创建一个Person对象列表用于演示Stream API的各种操作
        // 使用Arrays.asList()创建一个固定大小的列表，包含6个Person对象
        // 每个Person对象代表一个人，包含姓名、年龄、性别和职业四个属性
        List<Person> people = Arrays.asList(
            new Person("Alice", 25, "Female", "Engineer"),
            new Person("Bob", 30, "Male", "Manager"),
            new Person("Charlie", 35, "Male", "Engineer"),
            new Person("Diana", 28, "Female", "Designer"),
            new Person("Eve", 22, "Female", "Engineer"),
            new Person("Frank", 40, "Male", "Manager")
        );

        // 输出标题信息，标识这是Java Stream API增强功能的测试程序
        System.out.println("=== Java Stream API增强功能测试 ===");

        // 1. 基本过滤和映射操作
        // 演示如何使用filter()和map()方法对流进行基本操作
        // filter()方法用于筛选满足条件的元素，map()方法用于转换元素
        System.out.println("\n1. 基本过滤和映射操作:");
        // 从people列表创建一个流，然后进行以下操作:
        // 1. filter() - 筛选出职业为"Engineer"的人员
        // 2. map() - 将Person对象映射为姓名字符串
        // 3. collect() - 将流收集为List集合
        List<String> engineerNames = people.stream()
            .filter(p -> "Engineer".equals(p.getJob()))  // 过滤出工程师
            .map(Person::getName)  // 提取姓名
            .collect(Collectors.toList());  // 收集为List
        System.out.println("工程师姓名: " + engineerNames);

        // 2. 排序操作
        // 演示如何使用sorted()方法对流中的元素进行排序
        // Comparator.comparing()创建一个基于特定属性的比较器
        System.out.println("\n2. 排序操作:");
        // 对people流按照年龄进行排序，使用Comparator.comparing()方法创建比较器
        // 该比较器基于Person对象的getAge()方法返回值进行比较
        List<Person> sortedByAge = people.stream()
            .sorted(Comparator.comparing(Person::getAge))  // 按年龄排序
            .collect(Collectors.toList());  // 收集为List
        System.out.println("按年龄排序:");
        // 遍历排序后的列表并输出每个人员的姓名和年龄
        sortedByAge.forEach(p -> System.out.println("  " + p.getName() + " - " + p.getAge()));

        // 3. 归约操作
        // 演示如何使用reduce()和summaryStatistics()方法对流中的元素进行归约操作
        // 归约操作是将流中的多个元素合并为一个结果的操作
        System.out.println("\n3. 归约操作:");
        // 使用mapToInt()将Person对象流转换为int值流（年龄），然后使用reduce()方法计算总和
        // Integer::sum是方法引用，等价于(Integer a, Integer b) -> a + b
        OptionalInt totalAge = people.stream()
            .mapToInt(Person::getAge)  // 将Person流转换为年龄int流
            .reduce(Integer::sum);  // 计算年龄总和
        System.out.println("总年龄: " + totalAge.orElse(0));  // 如果有值则输出，否则输出0
        
        // 使用summaryStatistics()方法获取年龄的统计信息
        // 该方法返回IntSummaryStatistics对象，包含计数、最小值、最大值、总和和平均值
        IntSummaryStatistics stats = people.stream()
            .mapToInt(Person::getAge)  // 将Person流转换为年龄int流
            .summaryStatistics();  // 获取统计信息
        System.out.println("年龄统计: 平均=" + stats.getAverage() + 
                          ", 最大=" + stats.getMax() + 
                          ", 最小=" + stats.getMin() + 
                          ", 总数=" + stats.getCount());

        // 4. 分组操作
        // 演示如何使用Collectors.groupingBy()方法对流中的元素进行分组
        // groupingBy()类似于SQL中的GROUP BY操作
        System.out.println("\n4. 分组操作:");
        // 按职业对人员进行分组，Collectors.groupingBy()方法接收一个分类函数
        // Person::getJob是方法引用，表示按Person对象的getJob()方法返回值进行分组
        Map<String, List<Person>> groupByJob = people.stream()
            .collect(Collectors.groupingBy(Person::getJob));  // 按职业分组
        System.out.println("按职业分组:");
        // 遍历分组结果，输出每组的人员姓名
        groupByJob.forEach((job, persons) -> {
            System.out.println("  " + job + ": " + 
                persons.stream().map(Person::getName).collect(Collectors.joining(", ")));
        });

        // 5. 分区操作
        // 演示如何使用Collectors.partitioningBy()方法对流中的元素进行分区
        // 分区是分组的特殊情况，只有两个分区：true和false
        System.out.println("\n5. 分区操作:");
        // 按年龄是否大于等于30岁对人员进行分区
        // partitioningBy()方法接收一个谓词（返回boolean值的函数）
        Map<Boolean, List<Person>> partitionByAge = people.stream()
            .collect(Collectors.partitioningBy(p -> p.getAge() >= 30));  // 按年龄分区
        System.out.println("按年龄分区 (>=30):");
        // 输出年龄>=30岁的人员姓名
        System.out.println("  年龄>=30: " + 
            partitionByAge.get(true).stream().map(Person::getName).collect(Collectors.joining(", ")));
        // 输出年龄<30岁的人员姓名
        System.out.println("  年龄<30: " + 
            partitionByAge.get(false).stream().map(Person::getName).collect(Collectors.joining(", ")));

        // 6. 并行流操作
        // 演示如何使用parallelStream()方法进行并行处理
        // 并行流可以利用多核处理器提高处理大量数据时的性能
        System.out.println("\n6. 并行流操作:");
        // 使用parallelStream()创建并行流，然后统计工程师人数
        // 并行流会将数据分成多个部分并行处理，最后合并结果
        long engineerCount = people.parallelStream()
            .filter(p -> "Engineer".equals(p.getJob()))  // 过滤出工程师
            .count();  // 统计数量
        System.out.println("工程师人数 (并行流): " + engineerCount);

        // 7. 流的连接操作
        // 演示如何使用Stream.concat()方法连接两个流
        // concat()方法可以将两个流合并为一个新的流
        System.out.println("\n7. 流的连接操作:");
        // 创建一个新的名字列表
        List<String> moreNames = Arrays.asList("Grace", "Henry", "Ivy");
        // 使用Stream.concat()连接两个流：
        // 1. people流中提取的姓名流
        // 2. moreNames流
        List<String> allNames = Stream.concat(
            people.stream().map(Person::getName),  // 提取people中的姓名
            moreNames.stream()  // moreNames流
        ).collect(Collectors.toList());  // 收集为List
        System.out.println("所有名字: " + allNames);

        // 8. 去重操作
        // 演示如何使用distinct()方法去除流中的重复元素
        // distinct()方法基于元素的equals()方法判断是否重复
        System.out.println("\n8. 去重操作:");
        // 创建一个包含重复职业的列表
        List<String> jobsWithDuplicates = Arrays.asList("Engineer", "Manager", "Engineer", "Designer", "Manager");
        // 使用distinct()方法去除重复的职业
        List<String> distinctJobs = jobsWithDuplicates.stream()
            .distinct()  // 去除重复元素
            .collect(Collectors.toList());  // 收集为List
        System.out.println("去重前的职业: " + jobsWithDuplicates);
        System.out.println("去重后的职业: " + distinctJobs);

        // 9. 限制和跳过操作
        // 演示如何使用limit()和skip()方法对流中的元素进行限制和跳过
        // limit(n)返回前n个元素，skip(n)跳过前n个元素
        System.out.println("\n9. 限制和跳过操作:");
        // 获取年龄最小的3个人
        List<Person> limitedPeople = people.stream()
            .sorted(Comparator.comparing(Person::getAge))  // 按年龄排序
            .limit(3)  // 限制为前3个元素
            .collect(Collectors.toList());  // 收集为List
        System.out.println("年龄最小的3个人:");
        limitedPeople.forEach(p -> System.out.println("  " + p.getName() + " - " + p.getAge()));

        // 跳过年龄最小的2个人
        List<Person> skippedPeople = people.stream()
            .sorted(Comparator.comparing(Person::getAge))  // 按年龄排序
            .skip(2)  // 跳过前2个元素
            .collect(Collectors.toList());  // 收集为List
        System.out.println("跳过年龄最小的2个人:");
        skippedPeople.forEach(p -> System.out.println("  " + p.getName() + " - " + p.getAge()));

        // 10. 匹配操作
        // 演示如何使用allMatch()、anyMatch()和noneMatch()方法检查流中元素的匹配情况
        // 这些方法返回boolean值，是Stream API中的短路操作
        System.out.println("\n10. 匹配操作:");
        // 检查是否所有人都是成年人（年龄>=18）
        boolean allAdult = people.stream()
            .allMatch(p -> p.getAge() >= 18);  // 检查所有元素是否满足条件
        System.out.println("所有人都是成年人: " + allAdult);

        // 检查是否有年龄小于25岁的人
        boolean hasYoungPerson = people.stream()
            .anyMatch(p -> p.getAge() < 25);  // 检查是否有元素满足条件
        System.out.println("有人年龄小于25岁: " + hasYoungPerson);

        // 检查是否没有人叫Zoe
        boolean noOneNamedZoe = people.stream()
            .noneMatch(p -> "Zoe".equals(p.getName()));  // 检查是否没有元素满足条件
        System.out.println("没有人叫Zoe: " + noOneNamedZoe);

        // 11. 查找操作
        // 演示如何使用max()和min()方法查找流中的最大值和最小值元素
        // 这些方法返回Optional对象，避免了null值的问题
        System.out.println("\n11. 查找操作:");
        // 查找年龄最大的人
        Optional<Person> oldestPerson = people.stream()
            .max(Comparator.comparing(Person::getAge));  // 查找最大值元素
        System.out.println("年龄最大的人: " + oldestPerson.map(Person::getName).orElse("无"));

        // 查找年龄最小的人
        Optional<Person> youngestPerson = people.stream()
            .min(Comparator.comparing(Person::getAge));  // 查找最小值元素
        System.out.println("年龄最小的人: " + youngestPerson.map(Person::getName).orElse("无"));

        // 12. 链式操作
        // 演示Stream API的链式操作特性
        // 多个中间操作可以连接在一起形成一个操作管道，最后通过终端操作产生结果
        System.out.println("\n12. 链式操作:");
        // 链式操作示例：筛选年龄>=25的女性，按姓名排序，提取姓名，连接为字符串
        String result = people.stream()
            .filter(p -> p.getAge() >= 25)  // 筛选年龄>=25的人
            .filter(p -> "Female".equals(p.getGender()))  // 筛选女性
            .sorted(Comparator.comparing(Person::getName))  // 按姓名排序
            .map(Person::getName)  // 提取姓名
            .collect(Collectors.joining(", ", "年龄>=25的女性: [", "]"));  // 连接为字符串
        System.out.println(result);

        // 13. Collectors增强功能
        // 演示Collectors类的更多功能，包括收集到特定集合类型、收集为Map、分组计数等
        System.out.println("\n13. Collectors增强功能:");
        // 收集到特定集合类型 - TreeSet可以自动排序
        TreeSet<String> sortedNames = people.stream()
            .map(Person::getName)  // 提取姓名
            .collect(Collectors.toCollection(TreeSet::new));  // 收集为TreeSet
        System.out.println("排序后的名字集合: " + sortedNames);

        // 收集为Map - 将姓名作为键，年龄作为值
        Map<String, Integer> nameAgeMap = people.stream()
            .collect(Collectors.toMap(Person::getName, Person::getAge));  // 收集为Map
        System.out.println("姓名-年龄映射: " + nameAgeMap);

        // 分组计数 - 统计每种职业的人数
        Map<String, Long> jobCount = people.stream()
            .collect(Collectors.groupingBy(Person::getJob, Collectors.counting()));  // 分组并计数
        System.out.println("各职业人数: " + jobCount);

        // 14. 数值流操作
        // 演示IntStream等数值流的使用
        // 数值流提供了处理基本数据类型流的专门方法，避免了装箱和拆箱的开销
        System.out.println("\n14. 数值流操作:");
        // 使用IntStream.range()创建一个范围流，筛选偶数并输出
        IntStream.range(1, 10)  // 创建1到9的范围流
            .filter(n -> n % 2 == 0)  // 筛选偶数
            .forEach(n -> System.out.println("偶数: " + n));  // 输出每个偶数

        // 计算平均年龄
        double averageAge = people.stream()
            .mapToDouble(Person::getAge)  // 将Person流转换为double流
            .average()  // 计算平均值
            .orElse(0.0);  // 如果没有元素则返回0.0
        System.out.println("平均年龄: " + averageAge);

        // 15. Optional增强功能
        // 演示Optional类的使用，避免null值相关的异常
        // Optional是Java 8引入的一个容器类，代表一个值存在或不存在
        System.out.println("\n15. Optional增强功能:");
        // 创建一个包含姓名的Optional对象
        Optional<String> optName = Optional.ofNullable(people.get(0).getName());
        // 使用orElse()方法提供默认值
        String displayName = optName.orElse("未知");  // 如果值存在则返回，否则返回"未知"
        System.out.println("显示名称: " + displayName);

        // 创建一个空的Optional对象
        Optional<String> emptyOpt = Optional.empty();
        // 使用orElseGet()方法提供通过Supplier获取的默认值
        String defaultName = emptyOpt.orElseGet(() -> "默认名称");  // 如果值不存在则通过Supplier获取默认值
        System.out.println("默认名称: " + defaultName);

        // 输出测试完成信息
        System.out.println("\n=== 测试完成 ===");
    }

    /**
     * 人员信息类，用于存储人员的基本信息
     * <p>
     * 该类是一个简单的POJO（Plain Old Java Object）类，包含姓名、年龄、性别和职业四个属性。
     * 提供了构造函数、getter方法和toString()方法，用于创建和操作Person对象。
     * 在Stream API测试中，Person对象作为数据源用于演示各种流操作。
     * </p>
     */
    static class Person {
        // 人员姓名属性
        private String name;
        // 人员年龄属性
        private int age;
        // 人员性别属性
        private String gender;
        // 人员职业属性
        private String job;

        /**
         * Person类的构造函数，用于创建Person对象
         * <p>
         * 该构造函数接收四个参数，分别用于初始化Person对象的姓名、年龄、性别和职业属性。
         * 所有参数都通过构造函数参数传递，符合不可变对象的设计原则，有助于避免并发问题。
         * </p>
         * @param name 人员姓名，不能为空
         * @param age 人员年龄，必须为正整数
         * @param gender 人员性别，通常为"Male"或"Female"
         * @param job 人员职业，不能为空
         */
        public Person(String name, int age, String gender, String job) {
            this.name = name;    // 初始化姓名
            this.age = age;      // 初始化年龄
            this.gender = gender; // 初始化性别
            this.job = job;      // 初始化职业
        }

        /**
         * 获取人员姓名
         * <p>
         * 该方法是name属性的getter方法，遵循JavaBean规范，用于获取Person对象的姓名属性值。
         * 在Stream API操作中，该方法经常通过方法引用Person::getName的形式使用，
         * 用于提取流中Person对象的姓名信息进行进一步处理。
         * </p>
         * @return 人员姓名字符串
         */
        public String getName() { return name; }
        
        /**
         * 获取人员年龄
         * <p>
         * 该方法是age属性的getter方法，遵循JavaBean规范，用于获取Person对象的年龄属性值。
         * 在Stream API操作中，该方法经常通过方法引用Person::getAge的形式使用，
         * 用于提取流中Person对象的年龄信息进行排序、过滤、统计等操作。
         * </p>
         * @return 人员年龄整数值
         */
        public int getAge() { return age; }
        
        /**
         * 获取人员性别
         * <p>
         * 该方法是gender属性的getter方法，遵循JavaBean规范，用于获取Person对象的性别属性值。
         * 在Stream API操作中，该方法经常通过方法引用Person::getGender的形式使用，
         * 用于提取流中Person对象的性别信息进行分组、过滤等操作。
         * </p>
         * @return 人员性别字符串
         */
        public String getGender() { return gender; }
        
        /**
         * 获取人员职业
         * <p>
         * 该方法是job属性的getter方法，遵循JavaBean规范，用于获取Person对象的职业属性值。
         * 在Stream API操作中，该方法经常通过方法引用Person::getJob的形式使用，
         * 用于提取流中Person对象的职业信息进行分组、过滤、统计等操作。
         * </p>
         * @return 人员职业字符串
         */
        public String getJob() { return job; }

        /**
         * 重写toString()方法，提供Person对象的字符串表示形式
         * <p>
         * 该方法按照标准格式返回Person对象的字符串表示，包含所有属性的名称和值。
         * 使用String.format()方法格式化字符串，使其更易读且格式统一。
         * 在调试和日志记录中，重写的toString()方法可以提供有用的信息。
         * </p>
         * @return Person对象的字符串表示形式
         */
        @Override
        public String toString() {
            // 使用String.format()方法格式化字符串，包含所有属性信息
            return String.format("Person{name='%s', age=%d, gender='%s', job='%s'}", 
                               name, age, gender, job);
        }
    }
}